home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.1.5 / modules / PlacesDBUtils.jsm < prev    next >
Text File  |  2009-11-08  |  26KB  |  590 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
  3.  * ***** BEGIN LICENSE BLOCK *****
  4.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5.  *
  6.  * The contents of this file are subject to the Mozilla Public License Version
  7.  * 1.1 (the "License"); you may not use this file except in compliance with
  8.  * the License. You may obtain a copy of the License at
  9.  * http://www.mozilla.org/MPL/
  10.  *
  11.  * Software distributed under the License is distributed on an "AS IS" basis,
  12.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13.  * for the specific language governing rights and limitations under the
  14.  * License.
  15.  *
  16.  * The Original Code is Places Database Utils code.
  17.  *
  18.  * The Initial Developer of the Original Code is
  19.  * Mozilla Corporation.
  20.  * Portions created by the Initial Developer are Copyright (C) 2008
  21.  * the Initial Developer. All Rights Reserved.
  22.  *
  23.  * Contributor(s):
  24.  *   Marco Bonardo <mak77@bonardo.net> (Original Author)
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. const Cc = Components.classes;
  41. const Ci = Components.interfaces;
  42. const Cr = Components.results;
  43. const Cu = Components.utils;
  44.  
  45. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  46.  
  47. let EXPORTED_SYMBOLS = [ "PlacesDBUtils" ];
  48.  
  49. ////////////////////////////////////////////////////////////////////////////////
  50. //// Constants
  51.  
  52. const IS_CONTRACTID = "@mozilla.org/widget/idleservice;1";
  53. const OS_CONTRACTID = "@mozilla.org/observer-service;1";
  54. const HS_CONTRACTID = "@mozilla.org/browser/nav-history-service;1";
  55. const BS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1";
  56. const TS_CONTRACTID = "@mozilla.org/timer;1";
  57. const SB_CONTRACTID = "@mozilla.org/intl/stringbundle;1";
  58. const TIM_CONTRACTID = "@mozilla.org/updates/timer-manager;1";
  59.  
  60. const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
  61.  
  62. const FINISHED_MAINTENANCE_NOTIFICATION_TOPIC = "places-maintenance-finished";
  63.  
  64. // Do maintenance after 10 minutes of idle.
  65. // We choose a small idle time because we must not prevent laptops from going
  66. // to standby, also we don't want to hit cpu/disk while the user is doing other
  67. // activities on the computer, like watching a movie.
  68. // So, we suppose that after 10 idle minutes the user is moving to another task
  69. // and we can hit without big troubles.
  70. const IDLE_TIMEOUT = 10 * 60 * 1000;
  71.  
  72. // Check for idle every 10 minutes and do maintenance if the user has been idle
  73. // for more than IDLE_TIMEOUT.
  74. const IDLE_LOOKUP_REPEAT = 10 * 60 * 1000;
  75.  
  76. // These are the seconds between each maintenance (24h).
  77. const MAINTENANCE_REPEAT =  24 * 60 * 60;
  78.  
  79. ////////////////////////////////////////////////////////////////////////////////
  80. //// nsPlacesDBUtils class
  81.  
  82. function nsPlacesDBUtils() {
  83.   //////////////////////////////////////////////////////////////////////////////
  84.   //// Smart getters
  85.  
  86.   this.__defineGetter__("_bms", function() {
  87.     delete this._bms;
  88.     return this._bms = Cc[BS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  89.   });
  90.  
  91.   this.__defineGetter__("_hs", function() {
  92.     delete this._hs;
  93.     return this._hs = Cc[HS_CONTRACTID].getService(Ci.nsINavHistoryService);
  94.   });
  95.  
  96.   this.__defineGetter__("_os", function() {
  97.     delete this._os;
  98.     return this._os = Cc[OS_CONTRACTID].getService(Ci.nsIObserverService);
  99.   });
  100.  
  101.   this.__defineGetter__("_idlesvc", function() {
  102.     delete this._idlesvc;
  103.     return this._idlesvc = Cc[IS_CONTRACTID].getService(Ci.nsIIdleService);
  104.   });
  105.  
  106.   this.__defineGetter__("_dbConn", function() {
  107.     delete this._dbConn;
  108.     return this._dbConn = Cc[HS_CONTRACTID].
  109.                           getService(Ci.nsPIPlacesDatabase).DBConnection;
  110.   });
  111.  
  112.   this.__defineGetter__("_bundle", function() {
  113.     delete this._bundle;
  114.     return this._bundle = Cc[SB_CONTRACTID].
  115.                           getService(Ci.nsIStringBundleService).
  116.                           createBundle(PLACES_STRING_BUNDLE_URI);
  117.   });
  118.  
  119.   // register the maintenance timer
  120.   try {
  121.     let tim = Cc[TIM_CONTRACTID].getService(Ci.nsIUpdateTimerManager);
  122.     tim.registerTimer("places-maintenance-timer", this, MAINTENANCE_REPEAT);
  123.   } catch (ex) {
  124.     // The timer manager is not available in xpc shell tests
  125.   }
  126. }
  127.  
  128. nsPlacesDBUtils.prototype = {
  129.   _idleLookupTimer: null,
  130.   _statementsRunningCount: 0,
  131.  
  132.   //////////////////////////////////////////////////////////////////////////////
  133.   //// nsISupports
  134.  
  135.   QueryInterface: XPCOMUtils.generateQI([
  136.     Ci.nsITimerCallback,
  137.     Ci.nsIObserver,
  138.   ]),
  139.  
  140.   //////////////////////////////////////////////////////////////////////////////
  141.   //// nsITimerCallback
  142.  
  143.   notify: function PDBU_notify(aTimer) {
  144.     switch (aTimer) {
  145.       case this._idleLookUpTimer:
  146.         let idleTime = 0;
  147.         try {
  148.           idleTime = this._idlesvc.idleTime;
  149.         } catch (ex) {}
  150.  
  151.         // do maintenance on idle
  152.         if (idleTime > IDLE_TIMEOUT) {
  153.           // Stop the timer, we do maintenance once per day
  154.           this._idleLookUpTimer.cancel();
  155.           this._idleLookUpTimer = null;
  156.  
  157.           // start the cleanup
  158.           this.maintenanceOnIdle();
  159.         }
  160.         break;
  161.       default:
  162.         // Start the idle lookup timer
  163.         this._idleLookUpTimer = Cc[TS_CONTRACTID].createInstance(Ci.nsITimer);
  164.         this._idleLookUpTimer.initWithCallback(this, IDLE_LOOKUP_REPEAT,
  165.                                             Ci.nsITimer.TYPE_REPEATING_SLACK);
  166.         break;
  167.     }
  168.   },
  169.  
  170.   //////////////////////////////////////////////////////////////////////////////
  171.   //// mozIStorageStatementCallback
  172.  
  173.   handleError: function PDBU_handleError(aError) {
  174.     Cu.reportError("Async statement execution returned with '" +
  175.                    aError.result + "', '" + aError.message + "'");
  176.   },
  177.  
  178.   handleCompletion: function PDBU_handleCompletion(aReason) {
  179.     // We serve only the last statement completion
  180.     if (--this._statementsRunningCount > 0)
  181.       return;
  182.  
  183.     // We finished executing all statements.
  184.     // Sending Begin/EndUpdateBatch notification will ensure that the UI
  185.     // is correctly refreshed.
  186.     this._hs.runInBatchMode({runBatched: function(aUserData){}}, null);
  187.     this._bms.runInBatchMode({runBatched: function(aUserData){}}, null);
  188.     // Notify observers that maintenance tasks are complete
  189.     this._os.notifyObservers(null, FINISHED_MAINTENANCE_NOTIFICATION_TOPIC, null);
  190.   },
  191.  
  192.   //////////////////////////////////////////////////////////////////////////////
  193.   //// Tasks
  194.  
  195.   maintenanceOnIdle: function PDBU_maintenanceOnIdle() {
  196.     // bug 431558: preventive maintenance for Places database
  197.     let cleanupStatements = [];
  198.  
  199.     // MOZ_ANNO_ATTRIBUTES
  200.     // A.1 remove unused attributes
  201.     let deleteUnusedAnnoAttributes = this._dbConn.createStatement(
  202.       "DELETE FROM moz_anno_attributes WHERE id IN ( " +
  203.         "SELECT id FROM moz_anno_attributes n " +
  204.         "WHERE NOT EXISTS " +
  205.             "(SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1) " +
  206.           "AND NOT EXISTS " +
  207.             "(SELECT id FROM moz_items_annos WHERE anno_attribute_id = n.id LIMIT 1) " +
  208.       ")");
  209.     cleanupStatements.push(deleteUnusedAnnoAttributes);
  210.  
  211.     // MOZ_ANNOS
  212.     // B.1 remove annos with an invalid attribute
  213.     let deleteInvalidAttributeAnnos = this._dbConn.createStatement(
  214.       "DELETE FROM moz_annos WHERE id IN ( " +
  215.         "SELECT id FROM moz_annos a " +
  216.         "WHERE NOT EXISTS " +
  217.           "(SELECT id FROM moz_anno_attributes " +
  218.             "WHERE id = a.anno_attribute_id LIMIT 1) " +
  219.       ")");
  220.     cleanupStatements.push(deleteInvalidAttributeAnnos);
  221.  
  222.     // B.2 remove orphan annos
  223.     let deleteOrphanAnnos = this._dbConn.createStatement(
  224.       "DELETE FROM moz_annos WHERE id IN ( " +
  225.         "SELECT id FROM moz_annos a " +
  226.         "WHERE NOT EXISTS " +
  227.           "(SELECT id FROM moz_places_temp WHERE id = a.place_id LIMIT 1) " +
  228.         "AND NOT EXISTS " +
  229.           "(SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1) " +
  230.       ")");
  231.     cleanupStatements.push(deleteOrphanAnnos);
  232.  
  233.     // MOZ_BOOKMARKS_ROOTS
  234.     // C.1 fix missing Places root
  235.     //     Bug 477739 shows a case where the root could be wrongly removed
  236.     //     due to an endianness issue.  We try to fix broken roots here.
  237.     let selectPlacesRoot = this._dbConn.createStatement(
  238.       "SELECT id FROM moz_bookmarks WHERE id = :places_root");
  239.     selectPlacesRoot.params["places_root"] = this._bms.placesRoot;
  240.     if (!selectPlacesRoot.executeStep()) {
  241.       // We are missing the root, try to recreate it.
  242.       let createPlacesRoot = this._dbConn.createStatement(
  243.         "INSERT INTO moz_bookmarks (id, type, fk, parent, position, title) " +
  244.         "VALUES (:places_root, 2, NULL, 0, 0, :title)");
  245.       createPlacesRoot.params["places_root"] = this._bms.placesRoot;
  246.       createPlacesRoot.params["title"] = "";
  247.       cleanupStatements.push(createPlacesRoot);
  248.  
  249.       // Now ensure that other roots are children of Places root.
  250.       let fixPlacesRootChildren = this._dbConn.createStatement(
  251.         "UPDATE moz_bookmarks SET parent = :places_root WHERE id IN " +
  252.           "(SELECT folder_id FROM moz_bookmarks_roots " +
  253.             "WHERE folder_id <> :places_root)");
  254.       fixPlacesRootChildren.params["places_root"] = this._bms.placesRoot;
  255.       cleanupStatements.push(fixPlacesRootChildren);
  256.     }
  257.     selectPlacesRoot.finalize();
  258.  
  259.     // C.2 fix roots titles
  260.     //     some alpha version has wrong roots title, and this also fixes them if
  261.     //     locale has changed.
  262.     let updateRootTitleSql = "UPDATE moz_bookmarks SET title = :title " +
  263.                              "WHERE id = :root_id AND title <> :title";
  264.     // root
  265.     let fixPlacesRootTitle = this._dbConn.createStatement(updateRootTitleSql);
  266.     fixPlacesRootTitle.params["root_id"] = this._bms.placesRoot;
  267.     fixPlacesRootTitle.params["title"] = "";
  268.     cleanupStatements.push(fixPlacesRootTitle);
  269.     // bookmarks menu
  270.     let fixBookmarksMenuTitle = this._dbConn.createStatement(updateRootTitleSql);
  271.     fixBookmarksMenuTitle.params["root_id"] = this._bms.bookmarksMenuFolder;
  272.     fixBookmarksMenuTitle.params["title"] =
  273.       this._bundle.GetStringFromName("BookmarksMenuFolderTitle");
  274.     cleanupStatements.push(fixBookmarksMenuTitle);
  275.     // bookmarks toolbar
  276.     let fixBookmarksToolbarTitle = this._dbConn.createStatement(updateRootTitleSql);
  277.     fixBookmarksToolbarTitle.params["root_id"] = this._bms.toolbarFolder;
  278.     fixBookmarksToolbarTitle.params["title"] =
  279.       this._bundle.GetStringFromName("BookmarksToolbarFolderTitle");
  280.     cleanupStatements.push(fixBookmarksToolbarTitle);
  281.     // unsorted bookmarks
  282.     let fixUnsortedBookmarksTitle = this._dbConn.createStatement(updateRootTitleSql);
  283.     fixUnsortedBookmarksTitle.params["root_id"] = this._bms.unfiledBookmarksFolder;
  284.     fixUnsortedBookmarksTitle.params["title"] =
  285.       this._bundle.GetStringFromName("UnsortedBookmarksFolderTitle");
  286.     cleanupStatements.push(fixUnsortedBookmarksTitle);
  287.     // tags
  288.     let fixTagsRootTitle = this._dbConn.createStatement(updateRootTitleSql);
  289.     fixTagsRootTitle.params["root_id"] = this._bms.tagsFolder;
  290.     fixTagsRootTitle.params["title"] =
  291.       this._bundle.GetStringFromName("TagsFolderTitle");
  292.     cleanupStatements.push(fixTagsRootTitle);
  293.  
  294.     // MOZ_BOOKMARKS
  295.     // D.1 remove items without a valid place
  296.     // if fk IS NULL we fix them in D.7
  297.     let deleteNoPlaceItems = this._dbConn.createStatement(
  298.       "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
  299.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  300.       ") AND id IN (" +
  301.         "SELECT b.id FROM moz_bookmarks b " +
  302.         "WHERE fk NOT NULL AND b.type = :bookmark_type " +
  303.           "AND NOT EXISTS (SELECT url FROM moz_places_temp WHERE id = b.fk LIMIT 1) " +
  304.           "AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1) " +
  305.       ")");
  306.     deleteNoPlaceItems.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
  307.     cleanupStatements.push(deleteNoPlaceItems);
  308.  
  309.     // D.2 remove items that are not uri bookmarks from tag containers
  310.     let deleteBogusTagChildren = this._dbConn.createStatement(
  311.       "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
  312.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  313.       ") AND id IN (" +
  314.         "SELECT b.id FROM moz_bookmarks b " +
  315.         "WHERE b.parent IN " +
  316.           "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " +
  317.           "AND b.type <> :bookmark_type " +
  318.       ")");
  319.     deleteBogusTagChildren.params["tags_folder"] = this._bms.tagsFolder;
  320.     deleteBogusTagChildren.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
  321.     cleanupStatements.push(deleteBogusTagChildren);
  322.  
  323.     // D.3 remove empty tags
  324.     let deleteEmptyTags = this._dbConn.createStatement(
  325.       "DELETE FROM moz_bookmarks WHERE id NOT IN ( " +
  326.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  327.       ") AND id IN (" +
  328.         "SELECT b.id FROM moz_bookmarks b " +
  329.         "WHERE b.id IN " +
  330.           "(SELECT id FROM moz_bookmarks WHERE parent = :tags_folder) " +
  331.           "AND NOT EXISTS " +
  332.             "(SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1) " +
  333.       ")");
  334.     deleteEmptyTags.params["tags_folder"] = this._bms.tagsFolder;
  335.     cleanupStatements.push(deleteEmptyTags);
  336.  
  337.     // D.4 move orphan items to unsorted folder
  338.     let fixOrphanItems = this._dbConn.createStatement(
  339.       "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " +
  340.         "SELECT folder_id FROM moz_bookmarks_roots " +  // skip roots
  341.       ") AND id IN (" +
  342.         "SELECT b.id FROM moz_bookmarks b " +
  343.         "WHERE b.parent <> 0 " + // exclude Places root
  344.         "AND NOT EXISTS " +
  345.           "(SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1) " +
  346.       ")");
  347.     fixOrphanItems.params["unsorted_folder"] = this._bms.unfiledBookmarksFolder;
  348.     cleanupStatements.push(fixOrphanItems);
  349.  
  350.     // D.5 fix wrong keywords
  351.     let fixInvalidKeywords = this._dbConn.createStatement(
  352.       "UPDATE moz_bookmarks SET keyword_id = NULL WHERE id NOT IN ( " +
  353.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  354.       ") AND id IN ( " +
  355.         "SELECT id FROM moz_bookmarks b " +
  356.         "WHERE keyword_id NOT NULL " +
  357.           "AND NOT EXISTS " +
  358.             "(SELECT id FROM moz_keywords WHERE id = b.keyword_id LIMIT 1) " +
  359.       ")");
  360.     cleanupStatements.push(fixInvalidKeywords);
  361.  
  362.     // D.6 fix wrong item types
  363.     //     Folders, separators and dynamic containers should not have an fk.
  364.     //     If they have a valid fk convert them to bookmarks. Later in D.9 we
  365.     //     will move eventual children to unsorted bookmarks.
  366.     let fixBookmarksAsFolders = this._dbConn.createStatement(
  367.       "UPDATE moz_bookmarks SET type = :bookmark_type WHERE id NOT IN ( " +
  368.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  369.       ") AND id IN ( " +
  370.         "SELECT id FROM moz_bookmarks b " +
  371.         "WHERE type IN (:folder_type, :separator_type, :dynamic_type) " +
  372.           "AND fk NOTNULL " +
  373.       ")");
  374.     fixBookmarksAsFolders.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
  375.     fixBookmarksAsFolders.params["folder_type"] = this._bms.TYPE_FOLDER;
  376.     fixBookmarksAsFolders.params["separator_type"] = this._bms.TYPE_SEPARATOR;
  377.     fixBookmarksAsFolders.params["dynamic_type"] = this._bms.TYPE_DYNAMIC_CONTAINER;
  378.     cleanupStatements.push(fixBookmarksAsFolders);
  379.  
  380.     // D.7 fix wrong item types
  381.     //     Bookmarks should have an fk, if they don't have any, convert them to
  382.     //     folders.
  383.     let fixFoldersAsBookmarks = this._dbConn.createStatement(
  384.       "UPDATE moz_bookmarks SET type = :folder_type WHERE id NOT IN ( " +
  385.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  386.       ") AND id IN ( " +
  387.         "SELECT id FROM moz_bookmarks b " +
  388.         "WHERE type = :bookmark_type " +
  389.           "AND fk IS NULL " +
  390.       ")");
  391.     fixFoldersAsBookmarks.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
  392.     fixFoldersAsBookmarks.params["folder_type"] = this._bms.TYPE_FOLDER;
  393.     cleanupStatements.push(fixFoldersAsBookmarks);
  394.  
  395.     // D.8 fix wrong item types
  396.     //     Dynamic containers should have a folder_type, if they don't have any
  397.     //     convert them to folders.
  398.     let fixFoldersAsDynamic = this._dbConn.createStatement(
  399.       "UPDATE moz_bookmarks SET type = :folder_type WHERE id NOT IN ( " +
  400.         "SELECT folder_id FROM moz_bookmarks_roots " + // skip roots
  401.       ") AND id IN ( " +
  402.         "SELECT id FROM moz_bookmarks b " +
  403.         "WHERE type = :dynamic_type " +
  404.           "AND folder_type IS NULL " +
  405.       ")");
  406.     fixFoldersAsDynamic.params["dynamic_type"] = this._bms.TYPE_DYNAMIC_CONTAINER;
  407.     fixFoldersAsDynamic.params["folder_type"] = this._bms.TYPE_FOLDER;
  408.     cleanupStatements.push(fixFoldersAsDynamic);
  409.  
  410.     // D.9 fix wrong parents
  411.     //     Items cannot have dynamic containers, separators or other bookmarks
  412.     //     as parent, if they have bad parent move them to unsorted bookmarks.
  413.     let fixInvalidParents = this._dbConn.createStatement(
  414.       "UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN ( " +
  415.         "SELECT folder_id FROM moz_bookmarks_roots " +  // skip roots
  416.       ") AND id IN ( " +
  417.         "SELECT id FROM moz_bookmarks b " +
  418.         "WHERE EXISTS " +
  419.           "(SELECT id FROM moz_bookmarks WHERE id = b.parent " +
  420.             "AND type IN (:bookmark_type, :separator_type, :dynamic_type) " +
  421.             "LIMIT 1) " +
  422.       ")");
  423.     fixInvalidParents.params["unsorted_folder"] = this._bms.unfiledBookmarksFolder;
  424.     fixInvalidParents.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
  425.     fixInvalidParents.params["separator_type"] = this._bms.TYPE_SEPARATOR;
  426.     fixInvalidParents.params["dynamic_type"] = this._bms.TYPE_DYNAMIC_CONTAINER;
  427.     cleanupStatements.push(fixInvalidParents);
  428.  
  429. /* XXX needs test
  430.     // D.10 recalculate positions
  431.     //      This requires multiple related statements.
  432.     //      We can detect a folder with bad position values comparing the sum of
  433.     //      all position values with the triangular numbers obtained by the number
  434.     //      of children: (n * (n + 1) / 2). Starting from 0 is (n * (n - 1) / 2).
  435.     let detectWrongPositionsParents = this._dbConn.createStatement(
  436.       "SELECT parent FROM " +
  437.         "(SELECT parent, " +
  438.                 "(SUM(position) - (count(*) * (count(*) - 1) / 2)) AS diff " +
  439.         "FROM moz_bookmarks " +
  440.         "GROUP BY parent) " +
  441.       "WHERE diff <> 0");
  442.     while (detectWrongPositionsParents.executeStep()) {
  443.       let parent = detectWrongPositionsParents.getInt64(0);
  444.       // We will lose the previous position values and reposition items based
  445.       // on the ROWID value. Not perfect, but we can't rely on position values.
  446.       let fixPositionsForParent = this._dbConn.createStatement(
  447.         "UPDATE moz_bookmarks SET position = ( " +
  448.           "SELECT " +
  449.           "((SELECT count(*) FROM moz_bookmarks WHERE parent = :parent) - " +
  450.            "(SELECT count(*) FROM moz_bookmarks " +
  451.             "WHERE parent = :parent AND ROWID >= b.ROWID)) " +
  452.           "FROM moz_bookmarks b WHERE parent = :parent AND id = moz_bookmarks.id " +
  453.         ") WHERE parent = :parent");
  454.       fixPositionsForParent.params["parent"] = parent;
  455.       cleanupStatements.push(fixPositionsForParent);
  456.     }
  457. */
  458.  
  459.     // D.11 remove old livemarks status items
  460.     //      Livemark status items are now static but some livemark has still old
  461.     //      status items bookmarks inside it. We should remove them.
  462.     //      Note: This does not need to query the temp table.
  463.     let removeLivemarkStaticItems = this._dbConn.createStatement(
  464.       "DELETE FROM moz_bookmarks WHERE type = :bookmark_type AND fk IN ( " +
  465.         "SELECT id FROM moz_places WHERE url = :lmloading OR url = :lmfailed " +
  466.       ")");
  467.     removeLivemarkStaticItems.params["bookmark_type"] = this._bms.TYPE_BOOKMARK;
  468.     removeLivemarkStaticItems.params["lmloading"] = "about:livemark-loading";
  469.     removeLivemarkStaticItems.params["lmfailed"] = "about:livemark-failed";
  470.     cleanupStatements.push(removeLivemarkStaticItems);
  471.  
  472.     // MOZ_FAVICONS
  473.     // E.1 remove orphan icons
  474.     let deleteOrphanIcons = this._dbConn.createStatement(
  475.       "DELETE FROM moz_favicons WHERE id IN (" +
  476.         "SELECT id FROM moz_favicons f " +
  477.         "WHERE NOT EXISTS " +
  478.           "(SELECT id FROM moz_places_temp WHERE favicon_id = f.id LIMIT 1) " +
  479.           "AND NOT EXISTS" +
  480.           "(SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1) " +
  481.       ")");
  482.     cleanupStatements.push(deleteOrphanIcons);
  483.  
  484.     // MOZ_HISTORYVISITS
  485.     // F.1 remove orphan visits
  486.     let deleteOrphanVisits = this._dbConn.createStatement(
  487.       "DELETE FROM moz_historyvisits WHERE id IN (" +
  488.         "SELECT id FROM moz_historyvisits v " +
  489.         "WHERE NOT EXISTS " +
  490.           "(SELECT id FROM moz_places_temp WHERE id = v.place_id LIMIT 1) " +
  491.           "AND NOT EXISTS " +
  492.           "(SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1) " +
  493.       ")");
  494.     cleanupStatements.push(deleteOrphanVisits);
  495.  
  496.     // MOZ_INPUTHISTORY
  497.     // G.1 remove orphan input history
  498.     let deleteOrphanInputHistory = this._dbConn.createStatement(
  499.       "DELETE FROM moz_inputhistory WHERE place_id IN (" +
  500.         "SELECT place_id FROM moz_inputhistory i " +
  501.         "WHERE NOT EXISTS " +
  502.           "(SELECT id FROM moz_places_temp WHERE id = i.place_id LIMIT 1) " +
  503.           "AND NOT EXISTS " +
  504.           "(SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1) " +
  505.       ")");
  506.     cleanupStatements.push(deleteOrphanInputHistory);
  507.  
  508.     // MOZ_ITEMS_ANNOS
  509.     // H.1 remove item annos with an invalid attribute
  510.     let deleteInvalidAttributeItemsAnnos = this._dbConn.createStatement(
  511.       "DELETE FROM moz_items_annos WHERE id IN ( " +
  512.         "SELECT id FROM moz_items_annos t " +
  513.         "WHERE NOT EXISTS " +
  514.           "(SELECT id FROM moz_anno_attributes " +
  515.             "WHERE id = t.anno_attribute_id LIMIT 1) " +
  516.       ")");
  517.     cleanupStatements.push(deleteInvalidAttributeItemsAnnos);
  518.  
  519.     // H.2 remove orphan item annos
  520.     let deleteOrphanItemsAnnos = this._dbConn.createStatement(
  521.       "DELETE FROM moz_items_annos WHERE id IN ( " +
  522.         "SELECT id FROM moz_items_annos t " +
  523.         "WHERE NOT EXISTS " +
  524.           "(SELECT id FROM moz_bookmarks WHERE id = t.item_id LIMIT 1) " +
  525.       ")");
  526.     cleanupStatements.push(deleteOrphanItemsAnnos);
  527.  
  528.     // MOZ_KEYWORDS
  529.     // I.1 remove unused keywords
  530.     let deleteUnusedKeywords = this._dbConn.createStatement(
  531.       "DELETE FROM moz_keywords WHERE id IN ( " +
  532.         "SELECT id FROM moz_keywords k " +
  533.         "WHERE NOT EXISTS " +
  534.           "(SELECT id FROM moz_bookmarks WHERE keyword_id = k.id LIMIT 1) " +
  535.       ")");
  536.     cleanupStatements.push(deleteUnusedKeywords);
  537.  
  538.     // MOZ_PLACES
  539.     // L.1 fix wrong favicon ids
  540.     let fixInvalidFaviconIds = this._dbConn.createStatement(
  541.       "UPDATE moz_places SET favicon_id = NULL WHERE id IN ( " +
  542.         "SELECT id FROM moz_places h " +
  543.         "WHERE favicon_id NOT NULL " +
  544.           "AND NOT EXISTS " +
  545.             "(SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1) " +
  546.       ")");
  547.     cleanupStatements.push(fixInvalidFaviconIds);
  548.  
  549. /* XXX needs test
  550.     // L.2 recalculate visit_count
  551.     // We're detecting errors only in disk table since temp tables could have
  552.     // different values based on the number of visits not yet synced to disk.
  553.     let detectWrongCountPlaces = this._dbConn.createStatement(
  554.       "SELECT id FROM moz_places h " +
  555.       "WHERE id NOT IN (SELECT id FROM moz_places_temp) " +
  556.         "AND h.visit_count <> " +
  557.           "(SELECT count(*) FROM moz_historyvisits " +
  558.             "WHERE place_id = h.id AND visit_type NOT IN (0,4,7))");
  559.     while (detectWrongCountPlaces.executeStep()) {
  560.       let placeId = detectWrongCountPlaces.getInt64(0);
  561.  
  562.       let fixCountForPlace = this._dbConn.createStatement(
  563.         "UPDATE moz_places_view SET visit_count = ( " +
  564.           "(SELECT count(*) FROM moz_historyvisits " +
  565.             "WHERE place_id = :place_id AND visit_type NOT IN (0,4,7)) + " +
  566.           "(SELECT count(*) FROM moz_historyvisits_temp " +
  567.             "WHERE place_id = :place_id AND visit_type NOT IN (0,4,7)) + " +
  568.         ") WHERE id = :place_id");
  569.       fixCountForPlace.params["place_id"] = placeId;
  570.       cleanupStatements.push(fixCountForPlace);
  571.     }
  572. */
  573.  
  574.     // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!
  575.  
  576.     // Used to keep track of last call to handleCompletion
  577.     this._statementsRunningCount = cleanupStatements.length;
  578.     // Statements are automatically queued-up by mozStorage
  579.     cleanupStatements.forEach(function (aStatement) {
  580.         aStatement.executeAsync(this);
  581.         aStatement.finalize();
  582.       }, this);
  583.   },
  584. };
  585.  
  586. __defineGetter__("PlacesDBUtils", function() {
  587.   delete this.PlacesDBUtils;
  588.   return this.PlacesDBUtils = new nsPlacesDBUtils;
  589. });
  590.